﻿using System;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Administration.Health;
using CabLib;

namespace SPHealth.SharePoint.HealthRules.SP.Rules.Configuration
{
    class WarnAssembliesCompiledInDebug : SPHealthAnalysisRule
    {

        // This rule is based on 'Custom Health Rule for SharePoint 2010 that checks for Debug build assemblies'
        // publish by Wictor Wilén at http://www.wictorwilen.se/Post/Custom-Health-Rule-for-SharePoint-2010-that-checks-for-Debug-build-assemblies.aspx

        #region Locals
        string naughty_list = "";
        private const string ruleTitle = "WarnAssembliesCompiledInDebug";
        private const string IgnoreList = "IgnoreList";
        #endregion

        #region Constructor
        /* This rules does not require a constructor */
        #endregion

        #region Fields

        // the category to display this rule under
        public override SPHealthCategory Category
        {
            get { return SPHealthCategory.Performance; }
        }

        // the type or error to flag this rule as
        public override SPHealthCheckErrorLevel ErrorLevel
        {
            get { return SPHealthCheckErrorLevel.Warning; }
        }

        // the Explanation for the rule and what is not working
        public override string Explanation
        {
            get { return string.Format("The following solutions are deployed to the farm but contain assemblies that have been compiled in Debug mode:\n\r\n\r{0}", naughty_list); }
        }

        // how to fix the issue
        public override string Remedy
        {
            get { return "Recompile and redeploy the solutions in Release mode. Assemblies running in Debug mode consume more system resources and are less performant that assemblies compiled in Release mode."; }
        }

        // the Summary for the rule
        public override string Summary
        {
            get { return "One or more Solutions are installed that contain assemblies compiled in Debug mode."; }
        }

        public override SPHealthAnalysisRuleAutomaticExecutionParameters AutomaticExecutionParameters
        {
            get
            {
                SPHealthAnalysisRuleAutomaticExecutionParameters retval = new SPHealthAnalysisRuleAutomaticExecutionParameters();
                retval.Schedule = SPHealthCheckSchedule.Daily;
                retval.Scope = SPHealthCheckScope.Any;
                retval.ServiceType = typeof(SPTimerService);
                return retval;
            }
        }
        #endregion

        #region Methods

        // used to provide the configuration list item title
        public static string GetTitle()
        {
            // return the title for the rule
            return ruleTitle;
        }

        // used to provide the default configuration settings
        public static IDictionary<string, string> GetDefaultSettings()
        {
            // return the default settings for the rule
            IDictionary<string, string> settings = new Dictionary<string, string>();
            settings.Add("Comment1", "Enter a semi-colon delimited list of solution IDs");
            settings.Add("Comment2", "that should be excluded from this check");
            settings.Add(IgnoreList, "44be5f4a-d561-4981-a318-95abc706364a;");
            return settings;
        }

        // this method is called whenever the rule is executed
        public override SPHealthCheckStatus Check()
        {
            if (!SPFarm.Joined)
            {
                throw new InvalidOperationException();
            }

            // reset the results
            naughty_list = "";
            StringBuilder sBuilder = new StringBuilder();

            foreach (SPSolution solution in SPFarm.Local.Solutions)
            {
                // create a temporary local directory into which we can
                // download and extract the solution
                string tempPath = Path.GetTempPath();
                Guid guid = Guid.NewGuid();
                tempPath = string.Format(@"{0}{1:D}\", tempPath, guid);
                Directory.CreateDirectory(tempPath);

                // download the solution
                solution.SolutionFile.SaveAs(tempPath + solution.Name);

                // extract the solution
                new Extract().ExtractFile(tempPath + solution.Name, tempPath);

                // load the solution manifest as an XmlDocument
                NameTable nameTable = new NameTable();
                XmlNamespaceManager nsmgr = new XmlNamespaceManager(nameTable);
                nsmgr.AddNamespace("sp", "http://schemas.microsoft.com/sharepoint/");
                XmlDocument document = new XmlDocument();
                document.Load(tempPath + "manifest.xml");

                // find each assembly that is deployed by the solution
                foreach (XmlNode node in document.SelectNodes("//sp:Assemblies/sp:Assembly", nsmgr))
                {
                    XmlElement element = node as XmlElement;

                    // determine if we need to check this solution id
                    if (!common.GetRuleSetting(IgnoreList, ruleTitle).ToLower().Contains(solution.Id.ToString("d").ToLower()))
                    {
                        // determine if the assembly is in Debug mode
                        if (IsDebugBuild(tempPath + element.GetAttribute("Location")))
                        {
                            // we've found an assembly that is in debug mode, let's report it...
                            sBuilder.AppendLine(string.Format("{0} ({1}) - Assembly '{2}'", solution.Name, solution.Id, element.GetAttribute("Location")));
                            sBuilder.AppendLine(""); // throw a blank line to aid reading the list of failures.
                        }
                    }
                }

                naughty_list = sBuilder.ToString();

                // tidy up
                try
                {
                    Directory.Delete(tempPath, true);
                }
                catch
                { /* TO DO */}
            }
            if (string.IsNullOrEmpty(naughty_list))
                return SPHealthCheckStatus.Passed;

            return SPHealthCheckStatus.Failed;
        }

        public static bool IsDebugBuild(string AssemblyName)
        {
            bool retval = true; // fail safe

            object[] customAttributes = Assembly.LoadFrom(AssemblyName).GetCustomAttributes(typeof(DebuggableAttribute), false);
            if (customAttributes.Length != 0)
            {
                foreach (Attribute attribute in customAttributes)
                {
                    if (attribute is DebuggableAttribute)
                    {
                        DebuggableAttribute attribute2 = attribute as DebuggableAttribute;
                        retval = attribute2.IsJITOptimizerDisabled;
                    }
                }
            }

            customAttributes = null;

            return retval;
        }
        #endregion

    }
}
